using System;
using System.Collections.Generic;
using System.Text;
using NUnit.Framework;
using NUnit.Mocks;
using System.ComponentModel;
using Accountability4net;

namespace Accountability4net.UnitTests
{
    [TestFixture]
    public class InterfaceTests
    {
        /// <summary>
        /// Test that debit of an account will return the account value
        /// </summary>
        [Test]
        public void IAccount_Debit()
        {
            // Setup data
            Decimal amount = new Decimal(50.25);
            
            // Setup mocks
            DynamicMock account = new DynamicMock(typeof(IAccount));
            account.ExpectAndReturn("Debit", amount, amount, null);

            // Perform actions
            Decimal actual = (Decimal)account.Call("Debit", amount, null);
            
            // Asserts
            Assert.AreEqual(amount, actual);
            
            // Verify mocks
            account.Verify();
        }

        /// <summary>
        /// Account         Type Debit          Credit
        /// Assets          Increases           Decreases
        /// Liabilities     Decreases           Increases
        /// Income          Decreases           Increases
        /// Expenses        Increases           Decreases
        /// </summary>
        [Test]
        public void ITransaction_Debit_And_Credit_Accounts()
        {
            // Setup data
            IAccount bankAccount1 = new TestAccount(1, AccountType.ASSET);
            IAccount creditorAccount2 = new TestAccount(2, AccountType.LIABILITY);
            IAccount incomeAccount3 = new TestAccount(3, AccountType.INCOME);
            IAccount expenseAccount4 = new TestAccount(4, AccountType.EXPENSE);

            // Perform actions
            // First initialize the asset and liability accounts
            // Deposit 100.25 in the bank account
            bankAccount1.Debit(new Decimal(100.25), null);
            Assert.AreEqual(100.25, bankAccount1.Balance);

            // We owe creditor 50.75 say
            creditorAccount2.Credit(new Decimal(50.75), null);
            Assert.AreEqual(50.75, creditorAccount2.Balance);

            // Now inside a transaction we make 20.95 and deposit that to our bank account
            ITransaction transaction = new TestTransaction();
            transaction.Add(new TestAction(ActionType.CREDIT, incomeAccount3, new Decimal(20.95)));
            transaction.Add(new TestAction(ActionType.DEBIT, bankAccount1, new Decimal(20.95)));
            Assert.AreEqual(0, incomeAccount3.Balance);
            Assert.AreEqual(100.25, bankAccount1.Balance);
            
            transaction.Commit();
            Assert.AreEqual(20.95, incomeAccount3.Balance);
            Assert.AreEqual(121.20, bankAccount1.Balance);

        }

        private class TestAccount : IAccount
        {
            public TestAccount(int number, AccountType type)
            {
                balance_ = new Decimal(0);
                Number = number;
                Type = type;
            }

            #region IAccount Members

            public event PropertyChangedEventHandler PropertyChanged
            {
                add
                {
                }
                remove
                {
                }
            }

            private AccountType type_;
            public AccountType Type
            {
                get
                {
                    return type_;
                }
                set
                {
                    type_ = value;
                }
            }

            private int number_;
            public int Number
            {
                get
                {
                    return number_;
                }
                set
                {
                    number_ = value;
                }
            }

            private decimal Modify(decimal amount)
            {
                Decimal value = balance_;
                value += amount;
                if (value < 0)
                {
                    throw new ArgumentOutOfRangeException("Account balance cannot be below zero");
                }
                else
                {
                    balance_ = value;
                    return balance_;
                }
            }

            private void VerifyIsPositive(decimal amount)
            {
                if (amount < 0)
                {
                    throw new ArgumentOutOfRangeException("You cannot modify an account with a negative value");
                }
            }

            public decimal Debit(decimal amount, ITransaction transaction)
            {
                VerifyIsPositive(amount);
                if (Type == AccountType.LIABILITY
                    || Type == AccountType.INCOME)
                {
                    return Modify(-amount);
                }
                else // ASSET or EXPENSE
                {
                    return Modify(amount);
                }
            }

            public decimal Credit(decimal amount, ITransaction transaction)
            {
                VerifyIsPositive(amount);
                if (Type == AccountType.ASSET
                    || Type == AccountType.EXPENSE)
                {
                    return Modify(-amount);
                }
                else // LIABILITY or INCOME
                {
                    return Modify(amount);
                }
            }

            private Decimal balance_;
            public decimal Balance
            {
                get { return balance_; }
            }

            private string name_;
            public string Name
            {
                get
                {
                    return name_;
                }
                set
                {
                    name_ = value;
                }
            }

            private string description_;
            public string Description
            {
                get
                {
                    return description_;
                }
                set
                {
                    description_ = value;
                }
            }

            #endregion
        }

        private class TestAction : IAction
        {

            public TestAction(ActionType type, IAccount account, decimal amount)
            {
                Type = type;
                Account = account;
                Amount = amount;
            }

            #region IAction Members

            private decimal amount_;
            public decimal Amount
            {
                get
                {
                    return amount_;
                }
                set
                {
                    amount_ = value;
                }
            }

            private ActionType type_;
            public ActionType Type
            {
                get
                {
                    return type_;
                }
                set
                {
                    type_ = value;
                }
            }

            private IAccount account_;
            public IAccount Account
            {
                get
                {
                    return account_;
                }
                set
                {
                    account_ = value;
                }
            }

            private DateTimeOffset timestamp_;
            public DateTimeOffset Timestamp
            {
                get
                {
                    return timestamp_;
                }
                set
                {
                    timestamp_ = value;
                }
            }

            #endregion
        }

        private class TestTransaction : List<IAction>, ITransaction
        {
            #region ITransaction Members
            /// <summary>
            /// In a real implementation there would be isolation and rollback
            /// </summary>
            public void Commit()
            {
                foreach (IAction action in this)
                {
                    switch (action.Type)
                    {
                        case ActionType.CREDIT:
                            action.Account.Credit(action.Amount, this);
                            break;
                        case ActionType.DEBIT:
                            action.Account.Debit(action.Amount, this);
                            break;
                    };
                }
                isCommitted_ = true;
            }

            private DateTimeOffset timestamp_;
            public DateTimeOffset Timestamp
            {
                get
                {
                    return timestamp_;
                }
                set
                {
                    timestamp_ = value;
                }
            }

            private string description_;
            public string Description
            {
                get
                {
                    return description_;
                }
                set
                {
                    description_ = value;
                }
            }

            private bool isCommitted_;
            public bool IsCommitted
            {
                get { return isCommitted_; }
            }

            private Guid id_;
            public Guid ID
            {
                get
                {
                    return id_;
                }
                set
                {
                    id_ = value;
                }
            }

            #endregion

            #region INotifyPropertyChanged Members

            public event PropertyChangedEventHandler PropertyChanged;

            #endregion
        }

    }

}
